package org.softmed.reflection.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ReflectionUtil {

	public Object getFieldValue(Object obj, Class type, Field field)
			throws Throwable {
		String fieldName = field.getName();
		Method getter = getGetter(type, fieldName);
		return getter.invoke(obj, null);
	}

	public Object getFieldValue(Object obj, FieldInfo fieldInfo)
			throws Throwable {
		return fieldInfo.getGetter().invoke(obj, null);
	}

	public Field getField(Class type, String fieldName) {
		if (type == null)
			return null;
		Field field = null;
		try {
			field = type.getDeclaredField(fieldName);
		} catch (Throwable t) {
			return getField(type.getSuperclass(), fieldName);
		}
		return field;
	}

	public Method getGetter(Class type, String fieldName) throws Throwable {

		String fn = getFieldNameForGetterAndSetter(fieldName);
		Method getter = null;
		try {
			getter = type.getMethod("get" + fn, null);
		} catch (Throwable t) {
			try {
				getter = type.getMethod("is" + fn, null);
			} catch (Throwable t2) {
				return null;
			}
		}

		return getter;

	}

	public Method getSetter(Class type, Class<?> paramType, String fieldName)
			throws Throwable {
		String fn = getFieldNameForGetterAndSetter(fieldName);
		Method setter = null;
		Class[] params = new Class[1];
		params[0] = paramType;
		setter = type.getMethod("set" + fn, params);
		return setter;
	}

	protected String getFieldNameForGetterAndSetter(String fieldName) {
		String fn = fieldName.substring(0, 1).toUpperCase()
				+ fieldName.substring(1);
		return fn;
	}

	public Class isSpecificList(Field field) {
		Type type = field.getGenericType();
		if (type instanceof ParameterizedType) {
			ParameterizedType ptype = (ParameterizedType) field
					.getGenericType();
			Type[] targs = ptype.getActualTypeArguments();
			if (targs == null || targs.length == 0)
				return null;
			for (int j = 0; j < targs.length; j++) {
				Class tclas = (Class) targs[j];
				return tclas;
			}
		} else
			return null;
		return null;
	}

	public void setFieldValue(Object parent, Object child, Field field)
			throws Throwable {
		String fieldName = field.getName().toUpperCase();
		fieldName = fieldName.substring(0, 1) + field.getName().substring(1);
		setFieldValue(parent, child, fieldName);

	}

	public void setFieldValue(Object parent, Object child, String fieldName)
			throws Throwable {
		setFieldValue(parent.getClass(), parent, child, fieldName);
	}

	public void setFieldValue(Class parentType, Object parent, Object child,
			String fieldName) throws Throwable {

		Object[] args = new Object[1];
		args[0] = child;

		Method setter = getSetter(parentType, child.getClass(), fieldName);

		setter.invoke(parent, args);
	}

	public void setFieldValue(Object parent, Object child, Method setter)
			throws Throwable {

		Object[] args = new Object[1];
		args[0] = child;

		setter.invoke(parent, args);
	}

	public List<FieldInfo> getFieldInfos(Class type) throws Throwable {
		List<FieldInfo> infos = new ArrayList<FieldInfo>();
		Method[] methods = type.getMethods();
		for (Method getter : methods) {
			String name = getter.getName();
			if (name.startsWith("get") || name.startsWith("is")) {

				if (getter.getParameterTypes().length > 0
						|| getter.getReturnType() == void.class)
					continue;

				String fieldName = getFieldNameFromGetter(getter.getName());

				if (fieldName.equals("metaClass"))
					continue;

				Method setter = null;
				try {
					setter = getSetter(type, getter.getReturnType(), fieldName);
				} catch (Throwable t) {
					continue;
				}

				if (setter.getParameterTypes().length != 1)
					continue;

				if (setter.getParameterTypes()[0] != getter.getReturnType())
					continue;

				infos.add(new FieldInfo(fieldName, getter, setter, getter
						.getReturnType()));
			}
		}
		return infos;
	}

	public String getFieldNameFromGetter(String name) {
		String fieldName = null;
		if (name.startsWith("is"))
			fieldName = name.substring(2);
		if (name.startsWith("get"))
			fieldName = name.substring(3);

		fieldName = fieldName.substring(0, 1).toLowerCase()
				+ fieldName.substring(1);
		return fieldName;
	}

	public boolean isPrimitiveOrProxy(Class type) {
		return type.isPrimitive() || type == Integer.class
				|| type == Long.class || type == Double.class
				|| type == Short.class || type == Character.class
				|| type == Float.class || type == Double.class
				|| type == Byte.class;
	}

	public boolean isPrimitiveOrProxyOrString(Class type) {
		return type.isPrimitive() || type == Integer.class
				|| type == Long.class || type == Double.class
				|| type == Short.class || type == Character.class
				|| type == String.class || type == Float.class
				|| type == Double.class || type == Byte.class;
	}

	public List<FieldInfo> getNonNativeFieldInfos(Class type) throws Throwable {
		List<FieldInfo> infos = new ArrayList<FieldInfo>();
		Method[] methods = type.getMethods();
		for (Method getter : methods) {
			String name = getter.getName();
			if (name.startsWith("get") || name.startsWith("is")) {

				if (getter.getParameterTypes().length > 0
						|| getter.getReturnType() == void.class)
					continue;

				if (isPrimitiveOrProxyOrString(getter.getReturnType()))
					continue;

				String fieldName = getFieldNameFromGetter(getter.getName());
				
				if (fieldName.equals("metaClass"))
					continue;
				
				Method setter = null;
				try {
					setter = getSetter(type, getter.getReturnType(), fieldName);
				} catch (Throwable t) {
					continue;
				}

				if (setter.getParameterTypes().length != 1)
					continue;

				if (setter.getParameterTypes()[0] != getter.getReturnType())
					continue;

				infos.add(new FieldInfo(fieldName, getter, setter, getter
						.getReturnType()));
			}
		}
		return infos;
	}

	public List<FieldInfo> getNativeFieldInfos(Class type) throws Throwable {
		List<FieldInfo> infos = new ArrayList<FieldInfo>();
		Method[] methods = type.getMethods();
		for (Method getter : methods) {
			String name = getter.getName();
			if (name.startsWith("get") || name.startsWith("is")) {

				if (getter.getParameterTypes().length > 0
						|| getter.getReturnType() == void.class)
					continue;

				if (!isPrimitiveOrProxyOrString(getter.getReturnType()))
					continue;

				String fieldName = getFieldNameFromGetter(getter.getName());
				
				if (fieldName.equals("metaClass"))
					continue;
				
				Method setter = null;
				try {
					setter = getSetter(type, getter.getReturnType(), fieldName);
				} catch (Throwable t) {
					continue;
				}

				if (setter.getParameterTypes().length != 1)
					continue;

				if (setter.getParameterTypes()[0] != getter.getReturnType())
					continue;

				infos.add(new FieldInfo(fieldName, getter, setter, getter
						.getReturnType()));
			}
		}
		return infos;
	}

	public FieldInfo getFieldInfo(Class type, String fieldName)
			throws Throwable {
		Method getter = getGetter(type, fieldName);
		if (getter == null)
			return null;
		Method setter = getSetter(type, getter.getReturnType(), fieldName);

		if (getter.getParameterTypes().length > 0
				|| getter.getReturnType() == void.class)
			return null;

		if (setter.getParameterTypes().length != 1)
			return null;

		if (getter.getReturnType() != setter.getParameterTypes()[0])
			return null;

		return new FieldInfo(fieldName, getter, setter, getter.getReturnType());
	}

	public Object getFieldValue(Object obj, String fieldName) throws Throwable {
		FieldInfo info = getFieldInfo(obj.getClass(), fieldName);
		return getFieldValue(obj, info);
	}

	public String getHifenizedName(String sname) {
		byte[] bytes = sname.getBytes();
		byte schar;

		byte previousChar = bytes[0];
		int previousIndex = 0;

		boolean searching = false;
		String uri = "";
		String temp;

		for (int i = 0; i < bytes.length; i++) {
			schar = bytes[i];
			if (Character.isLowerCase(previousChar)
					&& Character.isUpperCase(schar)) {
				// Ex. mimeTypeHandler - 1 hit on TypeHandler
				temp = sname.substring(previousIndex, i);
				temp = temp.toLowerCase();

				if (previousIndex == 0)
					uri = temp;
				else
					uri += "-" + temp;
				previousIndex = i;
			} else if (Character.isUpperCase(previousChar)
					&& Character.isLowerCase(schar) && i != 1
					&& previousIndex + 1 != i) {
				// Ex. mimeTypeHandler - 1 hit on mimeType
				temp = sname.substring(previousIndex, i + 1);
				temp = temp.toLowerCase();

				if (previousIndex == 0)
					uri = temp;
				else
					uri += "-" + temp;
				previousIndex = i;
			} else if (i + 1 == bytes.length) {
				temp = sname.substring(previousIndex, i + 1);
				temp = temp.toLowerCase();

				if (previousIndex == 0)
					uri = temp;
				else
					uri += "-" + temp;
			}
			previousChar = schar;

		}
		return uri;
	}
}
